/*
 * Copyright (C) 2013 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY SCE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SCE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebTileProxy.h"

#if ENABLE(MANX_HTMLTILE)

#include "NativeWebMouseEvent.h"
#include "NotImplemented.h"
#include "WebContext.h"
#include "WebContextMenuProxy.h"
#include "WebFramePolicyListenerProxy.h"
#include "WebPageProxy.h"
#include "WebTileDrawingAreaProxy.h"
#include "WebTileManagerProxy.h"

using namespace WebCore;

namespace WebKit {

PassRefPtr<WebTileProxy> WebTileProxy::create(PassRefPtr<WebTileManagerProxy> tileManager, WebPageGroup* pageGroup)
{
    return adoptRef(new WebTileProxy(tileManager, pageGroup));
}

WebTileProxy::WebTileProxy(PassRefPtr<WebTileManagerProxy> tileManager, WebPageGroup* pageGroup)
    : m_tileManager(tileManager)
    , m_isVisible(false)
    , m_isFocused(true)
    , m_isClicked(false)
    , m_page(m_tileManager->webTilesContext()->createWebPage(this, pageGroup))
    , m_parentPage(0)
    , m_tileID(0)
{
    WKPageLoaderClient loaderClient = {0};
    loaderClient.version = kWKPageLoaderClientCurrentVersion;
    loaderClient.clientInfo = this;
    loaderClient.didFailProvisionalLoadWithErrorForFrame = didFailProvisionalLoadWithErrorForFrame;
    loaderClient.didFinishLoadForFrame = didFinishLoadForFrame;
    // Maybe for later if we want to track loading progress
//  loaderClient.didStartProgress = didStartProgress;
//  loaderClient.didChangeProgress = didChangeProgress;
//  loaderClient.didFinishProgress = didFinishProgress;
    m_page->initializeLoaderClient(&loaderClient);

    WKPagePolicyClient policyClient = {0};
    policyClient.version = kWKPagePolicyClientCurrentVersion;
    policyClient.clientInfo = this;
    policyClient.decidePolicyForNavigationAction = decidePolicyForNavigationAction;
    // Maybe for later if we want to track contents more closely
//    policyClient.decidePolicyForNewWindowAction = decidePolicyForNewWindowAction;
//    policyClient.decidePolicyForResponse = decidePolicyForResponse;
    m_page->initializePolicyClient(&policyClient);

    WKPageUIClient uiClient = {0};
    uiClient.version = kWKPageUIClientCurrentVersion;
    uiClient.clientInfo = this;
    uiClient.shouldInterruptJavaScript = shouldInterruptJavaScript;
    uiClient.exceededDatabaseQuota = exceededDatabaseQuota;
    m_page->initializeUIClient(&uiClient);

    m_page->setDrawsTransparentBackground(true);
}

WebTileProxy::~WebTileProxy()
{
}

WebPageProxy* WebTileProxy::parentPage() const
{
    return m_parentPage;
}

void WebTileProxy::setParentPage(WebPageProxy* parentPage)
{
    m_parentPage = parentPage;
}

uint64_t WebTileProxy::tileID() const
{
    return m_tileID;
}

void WebTileProxy::setTileID(uint64_t tileID)
{
    m_tileID = tileID;
}

IntSize WebTileProxy::tileSize() const
{
    return m_tileSize;
}

void WebTileProxy::setTileSize(int width, int height)
{
    const IntSize newSize(width, height);

    if (newSize == m_tileSize)
        return;

    const bool oldSizeIsZero = m_tileSize.isZero();
    m_tileSize = newSize;

    if (oldSizeIsZero) {
        m_page->initializeWebPage();

        // This reverts the initial m_focused(true) in the constructor, so that we start with an unfocused frame,
        // but with that frame having already been marked as the focus frame during initialization.
        // Otherwise we will get two window.onfocus events the first time we call setFocus(true)
        setFocused(false);
    } else if (m_page->drawingArea())
        m_page->drawingArea()->setSize(m_tileSize, IntSize(), IntSize());
}

bool WebTileProxy::isVisible() const
{
    return m_isVisible;
}

void WebTileProxy::setVisible(bool value)
{
    m_isVisible = value;

    m_page->viewStateDidChange(WebPageProxy::ViewIsVisible);
}

bool WebTileProxy::isFocused() const
{
    return m_isFocused;
}

void WebTileProxy::setFocused(bool value)
{
    m_isFocused = value;

    m_page->viewStateDidChange(WebPageProxy::ViewIsFocused);
}

bool WebTileProxy::isClicked() const
{
    return m_isClicked;
}

void WebTileProxy::setClicked(bool value)
{
    m_isClicked = value;

    Manx::MouseEvent::MouseEventType eventType = (value) ? Manx::MouseEvent::MouseDown : Manx::MouseEvent::MouseUp;
    Manx::MouseEvent mouseEvent(eventType, Manx::MouseEvent::LeftButton, -1000, -1000, 1, false, false, false, false);

    m_page->handleMouseEvent(NativeWebMouseEvent(mouseEvent));
}

void WebTileProxy::pause()
{
    m_page->suspendActiveDOMObjectsAndAnimations();
}

void WebTileProxy::resume()
{
    m_page->resumeActiveDOMObjectsAndAnimations();
}

void WebTileProxy::evalScript(const String& jsScript, uint64_t callbackID)
{
    RefPtr<StringCallback> resultCallback = StringCallback::create(new EvalScriptCallbackData { this, callbackID }, evalScriptCallback);
    m_page->runJavaScriptInMainFrame(jsScript, resultCallback.release());
}

const KURL& WebTileProxy::tileURL() const
{
    return m_tileURL;
}

void WebTileProxy::setTileURL(const String& urlString)
{
    const KURL url(KURL(), urlString);

    if (url == m_tileURL)
        return;

    m_tileURL = url;
    m_page->loadURL(url);
}

String WebTileProxy::actualURL() const
{
    return m_page->mainFrame() ? m_page->mainFrame()->url() : String();
}

void WebTileProxy::didUpdate()
{
    if (!m_page->drawingArea())
        return;

    static_cast<WebTileDrawingAreaProxy*>(m_page->drawingArea())->didUpdate();
}

// PageClient
PassOwnPtr<DrawingAreaProxy> WebTileProxy::createDrawingAreaProxy()
{
    return WebTileDrawingAreaProxy::create(page());
}

void WebTileProxy::setViewNeedsDisplay(const IntRect&)
{
    if (!m_page->drawingArea())
        return;

    const UpdateInfo* updateInfo = static_cast<WebTileDrawingAreaProxy*>(m_page->drawingArea())->pendingUpdateInfo();
    ASSERT(updateInfo);

    m_tileManager->dispatchTileUpdated(m_tileID, *updateInfo);
}

void WebTileProxy::displayView()
{
}

bool WebTileProxy::canScrollView()
{
    return false;
}

void WebTileProxy::scrollView(const IntRect&, const IntSize&)
{
    notImplemented();
}

IntSize WebTileProxy::viewSize()
{
    return tileSize();
}

bool WebTileProxy::isViewWindowActive()
{
    return false;
}

bool WebTileProxy::isViewFocused()
{
    return isFocused();
}

bool WebTileProxy::isViewVisible()
{
    return isVisible();
}

bool WebTileProxy::isViewInWindow()
{
    return true;
}

void WebTileProxy::processDidCrash()
{
    m_tileManager->dispatchTileCrashed(m_tileID);
}

void WebTileProxy::didRelaunchProcess()
{
}

void WebTileProxy::pageClosed()
{
}

void WebTileProxy::preferencesDidChange()
{
}

void WebTileProxy::toolTipChanged(const String&, const String&)
{
    notImplemented();
}

#if USE(TILED_BACKING_STORE)
void WebTileProxy::pageDidRequestScroll(const WebCore::IntPoint&)
{
    notImplemented();
}

void WebTileProxy::didRenderFrame(const WebCore::IntSize&, const WebCore::IntRect&)
{
    notImplemented();
}

void WebTileProxy::pageTransitionViewportReady()
{
    notImplemented();
}
#endif

void WebTileProxy::didChangeContentsSize(const WebCore::IntSize&)
{
    notImplemented();
}

void WebTileProxy::didChangeEditorState(int eventType, const WebKit::EditorState& editorState)
{
    notImplemented();
}

void WebTileProxy::didChangeCompositionState(const WebCore::IntRect& compositionRect)
{
    notImplemented();
}

void WebTileProxy::didChangeSelectionState(const String& selectedText, const WebCore::IntRect& selectedRect)
{
    notImplemented();
}

#if ENABLE(MANX_CURSOR_NAVIGATION)
void WebTileProxy::setCursorPosition(const IntPoint& cursorPosition)
{
    notImplemented();
}
#endif

void WebTileProxy::setCursor(const Cursor&)
{
    notImplemented();
}

void WebTileProxy::setCursorHiddenUntilMouseMoves(bool)
{
    notImplemented();
}

void WebTileProxy::didChangeViewportProperties(const ViewportAttributes&)
{
    notImplemented();
}

void WebTileProxy::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
{
    notImplemented();
}

void WebTileProxy::clearAllEditCommands()
{
    notImplemented();
}

bool WebTileProxy::canUndoRedo(WebPageProxy::UndoOrRedo)
{
    notImplemented();
    return false;
}

void WebTileProxy::executeUndoRedo(WebPageProxy::UndoOrRedo)
{
    notImplemented();
}

FloatRect WebTileProxy::convertToDeviceSpace(const FloatRect& viewRect)
{
    notImplemented();
    return viewRect;
}

FloatRect WebTileProxy::convertToUserSpace(const FloatRect& viewRect)
{
    notImplemented();
    return viewRect;
}

IntPoint WebTileProxy::screenToWindow(const IntPoint& point)
{
    notImplemented();
    return point;
}

IntRect WebTileProxy::windowToScreen(const IntRect& rect)
{
    notImplemented();
    return rect;
}

void WebTileProxy::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool)
{
    notImplemented();
}

#if ENABLE(GESTURE_EVENTS)
void WebTileProxy::doneWithGestureEvent(const WebGestureEvent&, bool)
{
    notImplemented();
}
#endif

#if ENABLE(TOUCH_EVENTS)
void WebTileProxy::doneWithTouchEvent(const NativeWebTouchEvent&, bool)
{
    notImplemented();
}
#endif

void WebTileProxy::doneWithMouseDownEvent(bool wasEventHandled)
{
}

void WebTileProxy::doneWithMouseUpEvent(bool wasEventHandled)
{
}

PassRefPtr<WebPopupMenuProxy> WebTileProxy::createPopupMenuProxy(WebPageProxy*)
{
    notImplemented();
    return 0;
}

PassRefPtr<WebContextMenuProxy> WebTileProxy::createContextMenuProxy(WebPageProxy*)
{
    notImplemented();
    return 0;
}

void WebTileProxy::setFindIndicator(PassRefPtr<FindIndicator>, bool, bool)
{
    notImplemented();
}

#if USE(ACCELERATED_COMPOSITING)
void WebTileProxy::enterAcceleratedCompositingMode(const LayerTreeContext&)
{
    notImplemented();
}

void WebTileProxy::exitAcceleratedCompositingMode()
{
    notImplemented();
}

void WebTileProxy::updateAcceleratedCompositingMode(const LayerTreeContext&)
{
    notImplemented();
}
#endif // USE(ACCELERATED_COMPOSITING)

void WebTileProxy::didChangeScrollbarsForMainFrame() const
{
    notImplemented();
}

void WebTileProxy::didCommitLoadForMainFrame(bool)
{
    notImplemented();
}

void WebTileProxy::didFinishLoadingDataForCustomRepresentation(const String&, const CoreIPC::DataReference&)
{
    notImplemented();
}

double WebTileProxy::customRepresentationZoomFactor()
{
    notImplemented();
    return 0;
}

void WebTileProxy::setCustomRepresentationZoomFactor(double)
{
    notImplemented();
}

void WebTileProxy::flashBackingStoreUpdates(const Vector<IntRect>&)
{
    notImplemented();
}

void WebTileProxy::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
{
    notImplemented();
}

void WebTileProxy::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
{
    notImplemented();
}

void WebTileProxy::handleAuthenticationRequiredRequest(WebFrameProxy*, const String&, const String&, const String&, const String&, PassRefPtr<Messages::WebPageProxy::AuthenticationRequiredRequest::DelayedReply>)
{
    notImplemented();
}

void WebTileProxy::handleCertificateVerificationRequest(WebFrameProxy*, uint32_t error, const String &url, const Vector<CString> &certificates, PassRefPtr<Messages::WebPageProxy::CertificateVerificationRequest::DelayedReply> reply)
{
    // Always deny URLs with invalid or untrusted certificates
    reply->send(false);

    // We must notify the tile of the error ourselves, as the response above will just discard the network request silently
    // Also, we start SSL error codes at 1000 to avoid overwriting libCurl's own error codes (both start at 1)
    tileManager()->dispatchLoadFailed(tileID(), 1000 + error);
}

void WebTileProxy::createNewPage(WebPageProxy *, const ResourceRequest &, const WindowFeatures &, WebEvent::Modifiers, WebMouseEvent::Button, PassRefPtr<Messages::WebPageProxy::CreateNewPage::DelayedReply> reply)
{
    notImplemented();
}

void WebTileProxy::runJavaScriptAlert(WebFrameProxy*, const String&, PassRefPtr<Messages::WebPageProxy::RunJavaScriptAlert::DelayedReply>)
{
    notImplemented();
}

void WebTileProxy::runJavaScriptComfirm(WebFrameProxy*, const String&,  PassRefPtr<Messages::WebPageProxy::RunJavaScriptConfirm::DelayedReply>)
{
    notImplemented();
}

void WebTileProxy::runJavaScriptPrompt(WebFrameProxy*, const String&, const String&, PassRefPtr<Messages::WebPageProxy::RunJavaScriptPrompt::DelayedReply>)
{
    notImplemented();
}

void WebTileProxy::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKTypeRef userData, const void *clientInfo)
{
    const WebTileProxy* thiz = static_cast<const WebTileProxy*>(clientInfo);
    ASSERT(thiz->page() == toImpl(page));

    if (toImpl(frame)->isMainFrame()) {
        thiz->tileManager()->dispatchLoadCompleted(thiz->tileID());
        // enable paint updates
        const_cast<WebTileProxy*>(thiz)->setVisible(true);
    }
}

void WebTileProxy::didFailProvisionalLoadWithErrorForFrame(WKPageRef page, WKFrameRef frame, WKErrorRef error, WKTypeRef userData, const void *clientInfo)
{
    const WebTileProxy* thiz = static_cast<const WebTileProxy*>(clientInfo);
    ASSERT(thiz->page() == toImpl(page));

    // Note: errorCode() is a CurlCode, see curl.h for the list of values
    if (toImpl(frame)->isMainFrame())
        thiz->tileManager()->dispatchLoadFailed(thiz->tileID(), toImpl(error)->errorCode());
}

void WebTileProxy::decidePolicyForNavigationAction(WKPageRef page, WKFrameRef frame, WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton, WKURLRequestRef request, WKFramePolicyListenerRef listener, WKTypeRef userData, const void* clientInfo)
{
    const WebTileProxy* thiz = static_cast<const WebTileProxy*>(clientInfo);

    ASSERT(thiz->page() == toImpl(page));

    WebFramePolicyListenerProxy* policyListener = toImpl(listener);

    // We allow back/forward, reload, and form resubmitted by default, since they imply we allowed the initial load
    if (navigationType == kWKFrameNavigationTypeBackForward
        || navigationType == kWKFrameNavigationTypeReload
        || navigationType == kWKFrameNavigationTypeFormResubmitted) {
        policyListener->use();
        return;
    }

    const KURL url = toImpl(request)->resourceRequest().url();

    WebFrameProxy* webframe = toImpl(frame);

    // Detect initial document load for the main frame and iframes
    // we don't forward such requests, just automatically allow them
    if (webframe->url().isEmpty()) {
        // printf("WebTileProxy: initial load for %s frame: %s\n", webframe->isMainFrame() ? "main" : "child", url.string().latin1().data());
        policyListener->use();
        return;
    }

    // We only allow location changes on the main frame
    if (webframe->isMainFrame()) {
        bool locationChangeAllowed = false;
        // The callback is also called whenever we call loadURL from setTileURL(),
        // we don't forward such requests, just automatically allow them
        if (url == thiz->tileURL()) {
            // printf("WebTileProxy: detect load from tile.loadURL: %s\n", url.string().latin1().data());
            locationChangeAllowed = true;
        } else {
            // printf("WebTileProxy: detect load from window.location: %s\n", url.string().latin1().data());

            // We allow fragment+query navigation within the page, e.g.:
            // http://www.foo.com/tile.html -> http://www.foo.com/tile.html#a
            // http://www.foo.com/tile.html#a -> http://www.foo.com/tile.html#b
            // http://www.foo.com/tile.html -> http://www.foo.com/tile.html?foo
            // http://www.foo.com/tile.html?foo -> http://www.foo.com/tile.html?foo&bar=1
            if (protocolHostAndPortAreEqual(url, thiz->tileURL()) && url.path() == thiz->tileURL().path())
                locationChangeAllowed = true;
            else {
                // To avoid a synchronous request which may stall the UI process, we fire this as an
                // asynchronous request instead, and consider the request denied.
                // If the JS onlocationchange handler allows the change, the tile will send a loadURL message as
                // if it would have been a simple url change on the tile side. This is effectively the same as
                // allowing the request and loading the new page, but without blocking on the UI side.
                thiz->tileManager()->dispatchLocationChangeRequested(thiz->tileID(), url);
            }
        }

        if (locationChangeAllowed) {
            // printf("URL change OK\n");
            policyListener->use();
            return;
        }
    } else {
        // printf("WebTileProxy: detect load from frame.location: %s\n", url.string().latin1().data());

        // We don't allow any modifications to iframes, except for fragments and query arguments
        const KURL iframeURL(KURL(), webframe->url());
        if (protocolHostAndPortAreEqual(url, iframeURL) && url.path() == iframeURL.path()) {
            // printf("URL change OK\n");
            policyListener->use();
            return;
        }
    }

    // printf("URL change DENIED\n");
    policyListener->ignore();
}

bool WebTileProxy::shouldInterruptJavaScript(WKPageRef page, const void *clientInfo)
{
    const WebTileProxy* thiz = static_cast<const WebTileProxy*>(clientInfo);
    ASSERT(thiz->page() == toImpl(page));

    thiz->tileManager()->dispatchTileUnresponsive(thiz->tileID());

    // We automatically interrupt execution of the misbehaving tile
    return true;
}

unsigned long long WebTileProxy::exceededDatabaseQuota(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKStringRef databaseName, WKStringRef displayName, unsigned long long currentQuota, unsigned long long currentOriginUsage, unsigned long long currentDatabaseUsage, unsigned long long expectedUsage, const void *clientInfo)
{
    // Allow 5MB of storage by default
    return 5 * 1024 * 1024;
}

void WebTileProxy::evalScriptCallback(WKStringRef stringRef, WKErrorRef, void* context)
{
    EvalScriptCallbackData* data = static_cast<EvalScriptCallbackData*>(context);
    if (data->callbackID)
        data->thiz->tileManager()->dispatchRunJavaScriptResultCallback(data->thiz->tileID(), data->callbackID, stringRef ? toImpl(stringRef)->string() : String());

    delete data;
}

} // namespace WebKit

#endif // ENABLE(MANX_HTMLTILE)
